﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Editor
{
    namespace Context
    {
        public enum LockedAxisMode
        {
            X,
            Y,
            Z
        }

        public enum TranformAxisMode
        {
            None,
            Move,
            Rotate
        }

        interface SharedData
        {
            Game.Set<Context.LockedAxisMode> LockedAxisModeSet
            {
                get;
            }

            TranformAxisMode TranformMode
            {
                get;
            }

            IDK.Camera Camera
            {
                get;
                set;
            }

            IDK.Engine Engine
            {
                get;
                set;
            }

            IDK.SceneNode SceneObjects
            {
                get;
                set;
            }

            IDK.SceneNode SystemObjects
            {
                get;
                set;
            }

            IDK.SceneNode AxisNode
            {
                get;
                set;
            }

            Game.DataBase.Manager DataBaseManager
            {
                get;
                set;
            }

            UInt32 LevelId
            {
                get;
                set;
            }

            void SelectNode(IDK.SceneNode node, bool lookAt = false);
            void RemoveNode(IDK.SceneNode node);

            IDK.SceneNode AddMapObject(Game.DataBase.Entities.MapObject item);
        }

        struct MouseEvent
        {
            public enum ButtonCode
            {
                None,
                Left,
                Right,
                Middle
            };

            public ButtonCode Button;
            public IDK.Vector2 Position;

            public MouseEvent(ButtonCode btn, float x, float y)
            {
                Button = btn;
                Position.x = x;
                Position.y = y;
            }
        }

        class Base
        {
            public SharedData Data;

            public Base(SharedData data)
            {
                Data = data;
            }
            public IDK.Vector3 RayCastWithPlane(IDK.Vector2 xy, float offsetHeight)
            {
                //TODO: make method "BuildRay"
                IDK.Vector3 rayStart = Data.Camera.GetAbsolutePosition();

                IDK.Vector3 rayDir = Data.Camera.ScreenToVector(xy);
                rayDir = IDK.Vector3.Normalize(rayDir);

                IDK.Ray rayCast = new IDK.Ray(rayStart, rayDir);

                return rayCast.Intersect(new IDK.Plane(IDK.Vector3.UpY, 0), offsetHeight);
            }

            public IDK.Vector2 ToIDKScreenFromViewer(IDK.Vector2 xy)
            {
                IDK.Viewport viewport = Data.Engine.GetRenderer().GetViewport();
                IDK.Vector2 retVal = new IDK.Vector2(xy.x / viewport.Width, xy.y / viewport.Height);
                retVal.x *= 1024.0f;
                retVal.y *= 768.0f;
                return retVal;
            }

            public virtual Base Update(float dt)
            {
                return this;
            }

            public virtual Base MouseDown(MouseEvent evnt)
            {
                return this;
            }

            public virtual Base MouseUp(MouseEvent evnt)
            {
                return this;
            }

            public virtual Base MouseMove(MouseEvent evnt)
            {
                return this;
            }
        }

        class UnSelected : Base
        {
            public UnSelected(SharedData data)
                : base(data)
            {
                Data.SelectNode(null);
            }

            public IDK.SceneNode GetNodeIn2D(IDK.Vector2 xy)
            {
                IDK.Vector3 rayStart = Data.Camera.GetAbsolutePosition();

                IDK.Vector3 rayDir = Data.Camera.ScreenToVector(xy);
                rayDir = IDK.Vector3.Normalize(rayDir);

                return Data.Engine.GetCore().GetScene().GetNodeByRay(Data.SceneObjects, rayStart, rayDir);
            }

            public override Base MouseDown(MouseEvent evnt)
            {
                // selected?
                switch (evnt.Button)
                {
                    case MouseEvent.ButtonCode.Left:
                        {
                            IDK.Vector2 toIDKScreen = ToIDKScreenFromViewer(evnt.Position);
                            IDK.SceneNode sceneNode = GetNodeIn2D(toIDKScreen);

                            if (sceneNode != null)
                            {
                                return new Selected(this.Data, sceneNode);
                            }
                            return new UnSelected(this.Data);
                        }
                }
                return this;
            }
        }

        class Selected : UnSelected
        {
            public IDK.SceneNode SceneNode;


            public Selected(SharedData data, IDK.SceneNode node, bool lookAt = false)
                : base(data)
            {
                SceneNode = node;
                Data.SelectNode(node, lookAt);
            }

            public override Base MouseMove(MouseEvent evnt)
            {
                switch (evnt.Button)
                {
                    case MouseEvent.ButtonCode.Left:
                        return new Moving(this.Data, SceneNode, evnt.Position);
                }
                return this;
            }
        }

        class Moving : Selected
        {
            protected IDK.Vector3 DragPositionOffset = IDK.Vector3.Zero;
            protected IDK.Quaternion FromOreintation;
            protected IDK.Vector3 PreviousPosition;
            protected IDK.Vector3 PreviousDragPosition;

            protected IDK.Vector2 MousePosition = IDK.Vector2.Zero;

            public Moving(SharedData data, IDK.SceneNode node, IDK.Vector2 dragPos)
                : base(data, node)
            {
                //calc relative position

                FromOreintation = node.GetTransformation().GetLocalOrientation();

                PreviousPosition = node.GetTransformation().GetLocalPosition();

                IDK.Vector2 toIDKScreen = ToIDKScreenFromViewer(dragPos);

                PreviousDragPosition = RayCastWithPlane(toIDKScreen, PreviousPosition.y);

                DragPositionOffset = PreviousPosition - PreviousDragPosition;

                MousePosition = dragPos;
            }

            private void Translating()
            {
                //TEMP:
                IDK.Vector3 oldPos = SceneNode.GetTransformation().GetLocalPosition() - DragPositionOffset;
                IDK.Vector3 objPos = RayCastWithPlane(ToIDKScreenFromViewer(MousePosition), PreviousPosition.y);

                //apply locked mode
                foreach (Context.LockedAxisMode it in Data.LockedAxisModeSet)
                {
                    switch (it)
                    {
                        case LockedAxisMode.X:
                            objPos.x = oldPos.x;
                            break;
                        case LockedAxisMode.Y:
                            objPos.y = oldPos.y;
                            break;
                        case LockedAxisMode.Z:
                            objPos.z = oldPos.z;
                            break;
                    }
                }

                SceneNode.GetTransformation().SetLocalPosition(objPos + DragPositionOffset);
            }

            private void Rotating(float dt)
            {
                /*
                IDK.Viewport viewport = Data.Engine.GetRenderer().GetViewport();
                IDK.Vector2 from = new IDK.Vector2((LastMouse.x / viewport.Width), (LastMouse.y / viewport.Height));
                IDK.Vector2 to = new IDK.Vector2((MousePosition.x / viewport.Width), (MousePosition.y / viewport.Height));
                SceneNode.GetTransformation().SetLocalOrientation(IDK.Quaternion.TrackBall(PreviousOreintation, from, to));
                */

                //LastMouse = MousePosition;
                IDK.Vector3 newPoint = RayCastWithPlane(ToIDKScreenFromViewer(MousePosition), PreviousPosition.y);

                IDK.Vector3 direction = newPoint - PreviousDragPosition;
                direction.y = 0; //lock y axis
                IDK.Quaternion to = IDK.Quaternion.RotationTo(IDK.Vector3.UpZ, direction);
                

                if (direction.Length() > 0.1)
                {
                    FromOreintation = IDK.Quaternion.Lerp(IDK.Quaternion.InterpolationMode.LinearSpherical, FromOreintation, to, direction.Length() * dt);
                    SceneNode.GetTransformation().SetLocalOrientation(FromOreintation);
                }
            }

            public override Base Update(float dt)
            {
                switch (Data.TranformMode)
                {
                    case TranformAxisMode.Move:
                        Translating();
                        break;
                    case TranformAxisMode.Rotate:
                        Rotating(dt);
                        break;
                }

                //
                //Data.AxisNode.GetTransformation().SetLocalPosition(SceneNode.GetTransformation().GetWorldPosition());
                return this;
            }

            public override Base MouseMove(MouseEvent evnt)
            {
                MousePosition = evnt.Position;

                switch (evnt.Button)
                {
                    case MouseEvent.ButtonCode.None:
                        return new Selected(this.Data, SceneNode);
                }
                return this;
            }

            public override Base MouseUp(MouseEvent evnt)
            {
                return new Selected(this.Data, this.SceneNode);
            }
        }


        class RemoveMapObject : UnSelected
        {
            // DataBaseManager.DeleteMapObject();
            public RemoveMapObject(SharedData data, IDK.SceneNode node)
                : base(data)
            {
                //remove link
                data.RemoveNode(node);

                //remove data
                data.Engine.Lock(false);

                data.DataBaseManager.DeleteMapObject(data.DataBaseManager.FindMapObjects(node.GetName()));

                data.Engine.Unlock();
            }
        }

        class MakeMapObject : Moving
        {
            public MakeMapObject(SharedData data, Game.DataBase.Entities.Model dbModel)
                : base(data,
                AddNewObjectByModel(data, dbModel),
                new IDK.Vector2(
                    data.Engine.GetRenderer().GetViewport().Width / 2,
                    data.Engine.GetRenderer().GetViewport().Height / 2))
            {
                //Reset Offset
                DragPositionOffset = IDK.Vector3.Zero;
                PreviousPosition = IDK.Vector3.Zero;
            }


            public override Base MouseDown(MouseEvent evnt)
            {
                // selected?
                switch (evnt.Button)
                {
                    case MouseEvent.ButtonCode.Left:
                            return new UnSelected(this.Data);
                }
                return this;
            }

            public override Base MouseMove(MouseEvent evnt)
            {
                MousePosition = evnt.Position;
                return this;
            }

            static private IDK.SceneNode AddNewObjectByModel(SharedData data, Game.DataBase.Entities.Model model)
            {
                if (model != null)
                {
                    data.Engine.Lock(false);

                    var mapObject = data.DataBaseManager.GenMapObject(model.name, data.LevelId, model.id);

                    IDK.SceneNode node = data.AddMapObject(mapObject);

                    data.Engine.Unlock();

                    return node;
                }

                return null;
            }
        }
    }
}